﻿#include "experiment.h"
#include "ui_experiment.h"
#include <QMessageBox>
#include <QThread>
#include <QDateTime>
#include "log4z.h"
#include "shadow.h"
#include <QLayout>
#include "msgbox.h"
#include "zbase64.h"
#include "frminput2019.h"
#include "qtimer.h"
#include "bllchartreader.h"
#include "fileworker.h"
#include "datacenter.h"
#include "controlcenter.h"
#include "paintworker.h"
#include "vdsadapter.h"
#include "chartutils.h"
#include "build.h"
#include "diskchecker.h"
#include "materialchecker.h"
#include "samplechecker.h"
#include "paramchecker.h"
#include "vdsconfig.h"
#include "userlogger.h"
#include "systempreset.h"
#include "customprinter.h"

Experiment::Experiment(QWidget *parent)
    : IVdsPaint(parent)
    , ui(new Ui::Experiment)
    , chartExp(new QChart)
    , chartExpEnd(new QChart)
    , chartSampleTime(1)
{
    ui->setupUi(this);

    registerMetaType();

    bdf = BllDataFlow::getInstance();
    sqlHelper = SqlHelper::getInstance();
    expDurationTimer = new QTimer(this);
    expDurationTimer->setInterval(1000);    //1000ms
    expDurationPaintTimer = new QTimer(this);
    expDurationPaintTimer->setInterval(20);
    progressCount = 0;
    bCtrl = BllController::getInstance();

    //Signals and Slots
    connect(bdf, &BllDataFlow::changeExperimentPageIndexSignal, this, &Experiment::changeExperimentPageIndexSlot);
    connect(bdf, &BllDataFlow::loadEnabledParamtersSignal,this,&Experiment::loadEnabledParamters);
    connect(bdf, &BllDataFlow::insertExpInfoSignal, this,&Experiment::insertExpInfoSlot);
    connect(bdf, &BllDataFlow::retranslateUiSignal, this, &Experiment::retranslateUiSlot);
    connect(expDurationTimer, &QTimer::timeout,this, &Experiment::updateExpDuration);
    connect(expDurationPaintTimer, SIGNAL(timeout()), this, SLOT(update()));
    connect(bCtrl, &BllController::expResultPassSignal, this, &Experiment::expPassSlot);
    connect(bCtrl, &BllController::expResultFailSignal, this, &Experiment::expFailSlot);
    connect(bCtrl, &BllController::liquidDetectedSignal, this, &Experiment::liquidDetectedSlot);
    connect(bCtrl, &BllController::shkCheckResultSignal, this, &Experiment::shkCheckResultSlot);
    connect(bCtrl, &BllController::experimentStartSignal, this, &Experiment::experimentStartSlot);

    initWidget();
}

Experiment::~Experiment()
{
    delete chartExp;
    delete chartExpEnd;
    delete ui;
}

void Experiment::showEvent(QShowEvent *)
{
    this->activateWindow();

    // 重新加载参数，重置参数按钮状态
    loadEnabledParamters();
}

void Experiment::hideEvent(QHideEvent *)
{
}

void Experiment::selectEditAll(QLineEdit *edit)
{
    edit->selectAll();
    edit->setFocus(Qt::OtherFocusReason);
}

void Experiment::selectEditAll(CustomNumEdit *edit)
{
    edit->selectAll();
}

void Experiment::changeFocus(QWidget *edit)
{
    if (edit == ui->lineSampleName)
    {// 切换焦点到体积输入框
        selectEditAll(ui->lineVolume);
    }
    else if (edit == ui->lineVolume)
    {// 切换焦点到耗材录入框
        selectEditAll(ui->lineMaterialISN);
    }
    else if (edit == ui->lineMaterialISN)
    {
        bool fillSuccess = fillMaterialInfo();
        if (fillSuccess)
        {// 填充成功，取消耗材录入框输入焦点
            ui->lineMaterialISN->clearFocus();
        }
        else
        {// 填充失败，重新输入
            ui->lineMaterialISN->clear();
            ui->lineMaterialISN->selectAll();
            ui->lineMaterialISN->setFocus();
        }
    }
}

bool Experiment::eventFilter(QObject *watched, QEvent *event)
{
    if (watched->inherits("QLineEdit") || watched->inherits("CustomNumEdit")) {
        // 屏蔽输入法
        if (QEvent::InputMethodQuery == event->type()) {
            return true;
        }

        // 处理焦点切换
        if (QEvent::KeyPress == event->type()) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
            if ((keyEvent->key() == Qt::Key_Enter || keyEvent->text() == "\n") ||
                (keyEvent->key() == Qt::Key_Return || keyEvent->text() == "\r")) {
                QTimer::singleShot(0, this, [&, watched]() {
                    changeFocus(qobject_cast<QWidget *>(watched));
                });
            }
        } else if (QEvent::FocusOut == event->type()) {
            if (watched == ui->lineMaterialISN) {
                if (!fillMaterialInfo()) {
                    ui->lineMaterialISN->clear();
                }
            }
        }
    } else if ((watched == ui->divider || watched == ui->divider_2 || watched == ui->divider_3) && event->type() == QEvent::Paint) {
        paintDivider(qobject_cast<QWidget *>(watched));
    } else if (watched == ui->mask && event->type() == QEvent::Paint) {
        paintWave();
    }
    return QWidget::eventFilter(watched,event);     // 最后将事件交给上层对话框
}

/**
 * @brief Experiment::buildMessage 构建提示
 * @param message 提示信息
 */
void Experiment::buildMessage(const QString &message)
{
    ui->lblErrorTip->setText(message);
    bool showErrorTip = message.length() > 0;
    ui->lblErrorTip->setVisible(showErrorTip);
    ui->label_2->setVisible(showErrorTip);
    ui->label_8->setVisible(showErrorTip);
}

/**
 * @brief Experiment::buildGraphs 构建图表
 * @param data 波形数据点
 * @param length 波形数据点长度
 */
void Experiment::buildGraphs(const double *data, int length)
{
    QList<QPointF> points;
    for (int i = 0; i < length; ++i)
    {
        points.push_back(QPointF(i * chartSampleTime, data[i] ));
    }

    xAxisExp->setRange(0, chartSampleTime * length);
    seriesExp->replace(points);
}

/**
 * @brief Experiment::buildExpEndChart 构建实验完成图表，取实验过程中最后一组数据
 */
void Experiment::buildExpEndChart()
{
    xAxisExpEnd->setRange(xAxisExp->min(), xAxisExp->max());
    yAxisExpEnd->setRange(yAxisExp->min(), yAxisExp->max());
    yAxisExpEnd->setTickCount(yAxisExp->tickCount());
    seriesExpEnd->replace(seriesExp->points());
    ui->lblVoltExp_2->setText(ui->lblVoltExp->text());
    ui->lblPulseExp_2->setText(ui->lblPulseExp->text());
    ui->verticalWidget_2->setVisible(ui->verticalWidget->isVisible());
}

/**
 * @brief Experiment::initGraphs 初始化图表，不必手动释放对象
 */
void Experiment::initGraphs()
{
    // 创建曲线
    seriesExp = new QLineSeries(this);
    seriesExpEnd = new QLineSeries(this);

    // 创建坐标轴
    xAxisExp = new QValueAxis(this);
    yAxisExp = new QValueAxis(this);

    xAxisExpEnd = new QValueAxis(this);
    yAxisExpEnd = new QValueAxis(this);

    QBrush brush(QColor(152, 152, 152, 255));
    xAxisExp->setTitleBrush(brush);
    xAxisExp->setTitleText(tr("时间(μs)"));
    ui->label_36->setText(tr("电压(V)"));
    xAxisExp->setLabelFormat("%d");
    yAxisExp->setLabelFormat("%d");

    xAxisExpEnd->setTitleBrush(brush);
    xAxisExpEnd->setTitleText(tr("时间(μs)"));
    ui->label_43->setText(tr("电压(V)"));
    xAxisExpEnd->setLabelFormat("%d");
    yAxisExpEnd->setLabelFormat("%d");

    chartExp->setMargins(QMargins(10, 10, 10, 5));
    chartExp->addSeries(seriesExp);
    chartExp->legend()->setVisible(false);
    chartExp->addAxis(xAxisExp, Qt::AlignBottom);
    chartExp->addAxis(yAxisExp, Qt::AlignLeft);
    seriesExp->attachAxis(xAxisExp);
    seriesExp->attachAxis(yAxisExp);
    ui->chartExp->setChart(chartExp);

    chartExpEnd->setMargins(QMargins(10, 20, 10, 5));
    chartExpEnd->addSeries(seriesExpEnd);
    chartExpEnd->legend()->setVisible(false);
    chartExpEnd->addAxis(xAxisExpEnd, Qt::AlignBottom);
    chartExpEnd->addAxis(yAxisExpEnd, Qt::AlignLeft);
    seriesExpEnd->attachAxis(xAxisExpEnd);
    seriesExpEnd->attachAxis(yAxisExpEnd);
    ui->chartExpEnd->setChart(chartExpEnd);
}

/**
 * @brief Experiment::autosetAxiesRange 自适应坐标范围
 */
void Experiment::autosetAxiesRange()
{
    chartSampleTime = VdsAdapter().sampleTime(parameter.pulse);
    ChartUtils::autosetAxiesRange(yAxisExp, parameter.volt, 100, true);
    ChartUtils::autosetAxiesRange(xAxisExp, chartSampleTime * MAX_WAVE_SIZE, 10, false);
}

/**
 * @brief Experiment::clearGraphs 清除图表
 */
void Experiment::clearGraphs()
{
    seriesExp->clear();
    seriesExpEnd->clear();
}

/**
 * @brief Experiment::onPaintChart 绘制电压曲线
 * @param data 测量数据
 */
void Experiment::onPaintChart(const VdsMeasureDataPtr measureData)
{
    buildGraphs(measureData->waveData, measureData->waveSize);
    if (!measureData->overflow) buildMessage("");

    ui->lblVoltExp->setText(QString("%1V").arg(measureData->vrms));
    ui->lblPulseExp->setText(QString("%1μs").arg(measureData->pwid));

    // TODO: 增加用户权限
    ui->lblEleExp->setText(QString("%1mA").arg(measureData.data()[CurrentChannel].vrms));
    ui->label_54->setVisible(false);
    ui->lblEleExp->setVisible(false);
    ui->verticalWidget->setVisible(true);
    emit paintDone();
}

/**
 * @brief Experiment::onMeasureError 电压测量异常
 * @param vdsError 异常状态
 */
void Experiment::onMeasureError(VdsError vdsError)
{
    static const QMap<int, QString> vdsErrorMessage {
        { OpenSuccess,      "示波器连接成功" },
        { OpenFail,         "示波器连接失败" },
        { InitFail,         "示波器参数设置失败" },
        { ReadFail,         "示波器连接断开" },
        { MeasureFail,      "数据解析错误" },
        { DataEmpty,        "未检测到波形，请检查信号源/参数" },
        { DataOverflowCh1,  "ch1数据溢出，参数设置不合适" },
        { DataOverflowCh2,  "ch2数据溢出，参数设置不合适" },
        { WaveformTooShort, "波形点数太少，数据被过滤" }
    };

    static const QMap<int, const char *> logMessage {
        { OpenSuccess,      "OpenSuccess" },
        { OpenFail,         "OpenFail" },
        { InitFail,         "InitFail" },
        { ReadFail,         "ReadFail" },
        { MeasureFail,      "MeasureFail" },
        { DataEmpty,        "DataEmpty" },
        { DataOverflowCh1,  "DataOverflowCh1" },
        { DataOverflowCh2,  "DataOverflowCh2" },
        { WaveformTooShort, "WaveformTooShort" }
    };

    Q_ASSERT(vdsErrorMessage.contains(vdsError));

    if (DataEmpty != vdsError)
    {// 暂时屏蔽该故障
        LOGFMTD("VdsMeasure Message: %d -> %s", vdsError, logMessage.value(vdsError));
    }

#if 0
    // TODO: 增加用户权限
    buildMessage(vdsErrorMessage.value(vdsError));
#endif
}

void Experiment::onDataFileReady(const QString &path)
{
    Q_UNUSED(path)
}

void Experiment::paintDivider(QWidget *w)
{
    QPainter painter(w);
    QPen pen;
    QVector<qreal> dashes;
    qreal space = 3;
    dashes << 5 << space << 5 <<space;
    pen.setDashPattern(dashes);
    pen.setColor(QColor(222,233,238,255));
    painter.setPen(pen);
    painter.drawLine(20, 15, w->width() - 20, 15);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(QPen(QColor(149,201,221,255)));
    painter.setBrush(Qt::white);
    painter.drawEllipse(-15, 0, 34, 30);
    painter.drawEllipse(w->width() - 19, 0, 34, 30);
}

void Experiment::paintWave()
{// 绘制波浪
    static int waveFactor = 5;
    static bool waveTransUp = true;
    static int waveTransUpCount = 0;
    const int waveCountFactor = 5;
    static int waveOffetX = 0;

    waveOffetX++;
    if (waveOffetX >= waveCountFactor * 6) {
        waveOffetX = 0;
    }

    waveTransUpCount++;
    if (waveTransUpCount >= 8) {
        waveTransUpCount = 0;
        if (waveTransUp) {
            waveFactor++;
            if (waveFactor >= 5) {
                waveTransUp = false;
            }
        } else {
            waveFactor--;
            if (waveFactor <= -5) {
                waveTransUp = true;
            }
        }
    }

    int progressTotal = ui->mask->height();
    int progress = progressTotal - progressCount;
    QPainter painter(ui->mask);
    painter.setRenderHints(QPainter::Antialiasing);
    painter.setPen(QPen(QColor(246,227,222,255)));
    painter.setBrush(QColor(246,227,222,255));
    QPainterPath path;
    path.moveTo(120,0);
    path.lineTo(0,0);
    path.lineTo(0 - waveOffetX, progress);
    int waveMaxWidth = ui->mask->width() + waveCountFactor * 12;
    qsrand(time(NULL));
    for (int i = 1;(i+5)*waveCountFactor <= waveMaxWidth;i+=6) {
        int waveAnotherFactor1 = qrand() % 4 - 2;
        int waveAnotherFactor2 = qrand() % 4 - 2;
        int waveAnotherFactor3 = qrand() % 4 - 2;
        int waveAnotherFactor4 = qrand() % 4 - 2;
        path.cubicTo(QPointF(i * waveCountFactor - waveOffetX,progress - waveFactor + waveAnotherFactor1), QPointF((i+1) * waveCountFactor - waveOffetX,progress - waveFactor + waveAnotherFactor2), QPointF((i+2) * waveCountFactor - waveOffetX,progress));
        path.cubicTo(QPointF((i+3) * waveCountFactor - waveOffetX,progress + waveFactor + waveAnotherFactor3), QPointF((i+4) * waveCountFactor - waveOffetX,progress + waveFactor + waveAnotherFactor4), QPointF((i+5) * waveCountFactor - waveOffetX,progress));
    }
    path.lineTo(120,0);
    painter.drawPath(path);
}

void Experiment::startComponent()
{
    // 绘图组件
    PaintWorker *paintWorker = new PaintWorker(this);
    DataCenter::instance()->addComponent(ComponentPtr(paintWorker));

    // 文件保存组件
    FileWorker *fileWorker = new FileWorker;
    DataCenter::instance()->addComponent(ComponentPtr(fileWorker));

    // 电压测量组件
    bllChartReader *chartReader = new bllChartReader(parameter.interval);
    ControlCenter::instance()->addComponent(ComponentPtr(chartReader));

    setVdsParameter();
    DataCenter::instance()->start(&expInfo.voltageRmsDatapath);
    ControlCenter::instance()->start();
}

void Experiment::stopComponent()
{
    DataCenter::instance()->clearComponent();
    ControlCenter::instance()->clearComponent();
}

void Experiment::registerMetaType()
{
    qRegisterMetaType<VdsError>();
    qRegisterMetaType<VdsParameter>("VdsParameter");
    qRegisterMetaType<VdsMeasureData>("VdsMeasureData");
    qRegisterMetaType<VdsParameterPtr>("VdsParameterPtr");
    qRegisterMetaType<VdsMeasureDataPtr>("VdsMeasureDataPtr");
}

void Experiment::setVdsParameter()
{
    VdsConfig().setParameter(parameter.volt, parameter.pulse, parameter.interval);
}

/**
 * @brief Experiment::start 启动测量
 */
void Experiment::start()
{
    liquidDetected = false;
    autoStop = false;
    manualStop = false;
    sendStartCmd();

    buildMessage("");
    ui->verticalWidget->setVisible(false);

    clearGraphs();
    autosetAxiesRange();
    startComponent();
    bdf->endExperiment(false);
    bCtrl->setInstrumentStauts(Status::Experiment);
}

/**
 * @brief Experiment::stop 停止测量
 * @param success 是否正常结束
 */
void Experiment::stop(bool success)
{
    stopComponent();
    buildMessage("");
    bdf->endExperiment(true);
    bCtrl->setInstrumentStauts(success ? Status::Idle : Status::Error);
}

bool Experiment::fillMaterialInfo()
{
    bool fillSuccess = true;
    EttaError error = checkMaterial();
    if (error >= EttaError::EXP_MATERIAL_INVALID)
    {// 无效条码，清空解析
        material.name.clear();
        material.type.clear();
        material.batchNumber.clear();
        material.serialCode.clear();
        material.expirationDate.clear();
        fillSuccess = (EttaError::EXP_MATERIAL_EMPTY == error);
    }

    updateMaterialDetail(material);
    return fillSuccess;
}

EttaError Experiment::checkSampleName()
{
    sample.name = ui->lineSampleName->text().trimmed();
    return SampleChecker::checkSampleName(sample);
}

EttaError Experiment::checkMaterial()
{
    return MaterialChecker::checkMaterial(ui->lineMaterialISN->text().trimmed(), material);
}

EttaError Experiment::checkSampleVolume()
{
    return ui->lineVolume->text().isEmpty() ? EttaError::EXP_VOLUME_EMPTY : EttaError::OK;
}

EttaError Experiment::checkParamSelect()
{
    return ParamChecker::checkParamSelect(ui->gboxSelectParam);
}

void Experiment::clearInput()
{
    ui->lineMaterialISN->clear();
    ui->lineSampleName->clear();
    ui->lineVolume->clear();
    fillMaterialInfo();
    clearParamsSelect();
}

bool Experiment::checkInput()
{
    const QMap<EttaError, QString> inputErrorMessages = {
        {EXP_SAMPLE_EMPTY,           tr("Please input a sample first.")},
        {EXP_SAMPLE_ALREADY_EXIST,   tr("Sample already in use, continue?")},
        {EXP_MATERIAL_EMPTY,         tr("Please input a material first.")},
        {EXP_MATERIAL_ALREADY_EXIST, tr("Material already in use.")},
        {EXP_MATERIAL_INVALID,       tr("Material invalid.")},
        {EXP_MATERIAL_EXPIRE,        tr("Material expires.")},
        {EXP_MATERIAL_NOT_MATCH,     tr("Material not match!")},
        {EXP_PARAM_UNSELECTED,       tr("Please select a parameter first.")},
        {EXP_VOLUME_EMPTY,           tr("Please input a volume first.")}
    };

    QWidget *w = ui->lineSampleName;
    EttaError error = EttaError::OK;
    error = checkSampleName();

    reuseSample = false;

    if (EXP_SAMPLE_ALREADY_EXIST == error)
    {// 重复样本号，由用户选择确认是否开始实验
        QString htmlStr = "<p><span style='color: rgb(255, 0, 0); font-size: 24px;'>";
        htmlStr += inputErrorMessages[error];
        htmlStr += "</span></p>";
        if (QMessageBox::Ok != MsgBox::warning(this, tr("input check"), htmlStr))
        {
            ui->lineSampleName->selectAll();
            ui->lineSampleName->setFocus();
            return false;
        }

        error = EttaError::OK;
        reuseSample = true;
    }

    if ((EttaError::OK == error))
    {
        w = ui->lineVolume;
        error = checkSampleVolume();
        if (EttaError::OK == error)
        {
            w = ui->lineMaterialISN;
            error = checkMaterial();
            if ((EttaError::OK == error))
            {
                w = Q_NULLPTR;
                error = checkParamSelect();
            }
        }
    }

    if (EttaError::OK != error)
    {
        QString htmlStr = "<p><span style='color: rgb(255, 0, 0); font-size: 24px;'>";
        htmlStr += inputErrorMessages[error];
        htmlStr += "</span></p>";

        MsgBox::information(this, tr("input check"), htmlStr);

        if (w && w->inherits("QLineEdit"))
        {
            QLineEdit *lineEdit = qobject_cast<QLineEdit *>(w);
            lineEdit->selectAll();
            lineEdit->setFocus();
        }

        return false;
    }

    return true;
}

void Experiment::changeExperimentPageIndexSlot(int index)
{
    ui->stackedWidget->setCurrentIndex(index);
    if (index == 0)
    {
        clearInput();
    }
}

void Experiment::on_btnStartExp_clicked()
{
    frmInput2019::Instance()->hideAndClearFocus();

    if (DiskChecker::Exhaust == DiskChecker::checkFreeSpace(DRIVER_NAME))
    {
        return;
    }

    if (!checkInput())
    {
        return;
    }

    // 更新耗材信息
    updateMaterialDetail(material);

    // 更新样本信息
    updateSampleDetail(sample);

    // 更新参数信息
    QList<Parameter> pList = sqlHelper->selectParameter(parameter);
    parameter = pList[0];
    updateParameterDetail(parameter);

    //insert into database
    sqlHelper->insertMaterial(material);

    initExperimentInfo();

    start();
    bdf->changeExperimentPageIndex(1);
}


void Experiment::on_btnEndExp_clicked()
{
    MsgBox msgbox;
    msgbox.setTitle(QString(tr("Stop Experiment")));
    QString html = QString("<p><span style='color: rgb(255, 0, 0); font-size: 24px;'>%1</span></p>").arg(tr("Confirm to end experiment?"));
    msgbox.setText(html);
    msgbox.setInformativeText(QString(tr("If yes, this will be recorded as abnormal data.")));
    int result = msgbox.exec();
    if (result != QMessageBox::Ok)
    {
        return;
    }

    manualStop = true;
    ui->btnEndExp->setEnabled(false);
    ui->btnEndExp->setText(tr("Stop..."));
    ui->btnEndExp->setProperty("enabled", false);
    UserLogger::log(QString("%1: %2").arg(tr("Emergency Stop")).arg(sample.name));

    // 延时2s发送停止信号，保证下位机能正常响应
    QTimer::singleShot(2000, this, [&]() {
        //manual stop
        sendStopCmd();
        LOGFMTD("%s end experiment",currentUsername.toStdString().c_str());
    });
}

void Experiment::initWidget()
{
    // 初始化图表
    initGraphs();
    ui->stackedWidget->setCurrentIndex(0);

    setShadow({ui->widget_5,ui->widget_6,ui->widget_7,ui->widget_8,ui->widget_10});
    ui->btnStartExp->setEnabled(false);
    ui->lineVolume->installEventFilter(this);
    ui->lineSampleName->installEventFilter(this);
    ui->lineMaterialISN->installEventFilter(this);
    ui->lineMaterialISN->setProperty("noinput", true);
    ui->lineVolume->setProperty("flag", "number");
    ui->divider->installEventFilter(this);
    ui->divider_2->installEventFilter(this);
    ui->divider_3->installEventFilter(this);
    ui->mask->installEventFilter(this);
    ui->lblMaterialExpDate1->hide();
    ui->label_14->hide();

    btnLabelStyle = "QAbstractButton{border-image:none;background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(238,246,247,255), stop:0.85 rgba(238,246,247,255), stop:1 rgba(200,208,210,255));border:5px solid #e0ecee;border-radius:14px;}"
                    "QAbstractButton:checked{border-image:none;background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(18,83,171, 255), stop:0.85 rgba(0,208,228, 255), stop:1 rgba(0,185,193, 255));border:5px solid #c1e8f1;border-radius:14px;}";

    labelNameStyleUnchecked = "QLabel{color:#1E2B58;font-size:20px;font-weight:bold;}";
    labelCodeStyleUnchecked = "QLabel{color:#5c6887;font-size:20px;font-weight:bold;}";
    labelNameStyleChecked = "QLabel{color:white;font-size:20px;font-weight:bold;}";
    labelCodeStyleChecked = "QLabel{color:white;font-size:20px;font-weight:bold;}";

    // 为maskBox设置logo遮罩
    QPixmap pixmap(":/resources/masklogo.png");
    if (windowWidth >= 1280) {
        this->setProperty("cssSize", "large");
        ui->widget_10->layout()->setMargin(16);
        ui->btnEndExp->setMaximumWidth(280);
        ui->chartExp->setGeometry(0,0,640,500);
        ui->chartExpEnd->setGeometry(0,0,568,415);
        ui->labelsBox->setGeometry(0,0,640,100);
        ui->labelsBox_2->setGeometry(0,10,568,64);

        labelNameStyleUnchecked = "QLabel{color:#1E2B58;font-size:30px;font-weight:bold;}";
        labelCodeStyleUnchecked = "QLabel{color:#5c6887;font-size:30px;font-weight:bold;}";
        labelNameStyleChecked = "QLabel{color:white;font-size:30px;font-weight:bold;}";
        labelCodeStyleChecked = "QLabel{color:white;font-size:30px;font-weight:bold;}";

        pixmap = pixmap.scaledToHeight(116, Qt::FastTransformation);
    } else {
        pixmap = pixmap.scaledToHeight(80, Qt::FastTransformation);
    }
    ui->maskBox->setMask(pixmap.mask());

    ui->lineVolume->init(3, 100, 2, true);

    initBtnParam();
    clearParamsSelect();
}

void Experiment::initBtnParam()
{
    QList<LabelButton *> btns = ui->gboxSelectParam->findChildren<LabelButton*>();
    foreach(LabelButton * btn, btns)
    {
        btn->setChecked(false);
        btn->setStyleSheet(btnLabelStyle);
        btn->getNameLabelInstance()->setStyleSheet(labelNameStyleUnchecked);
        btn->getCodeLabelInstance()->setStyleSheet(labelCodeStyleUnchecked);
        connect(btn, &QAbstractButton::toggled, this, &Experiment::setBtnParamCheckedSlot);
    }
}

void Experiment::clearParamsSelect()
{
    //reset btnParam to initial state
    QList<LabelButton *> btns = ui->gboxSelectParam->findChildren<LabelButton*>();
    foreach(LabelButton * btn, btns)
    {
        //for autoExclusive buttons, we need do this
        if(btn->isChecked())
        {
            btn->setAutoExclusive(false);
            btn->setChecked(false);
            btn->setAutoExclusive(true);
        }
    }
}

void Experiment::loadEnabledParamters()
{
    Parameter p;
    p.ison = true;
    QList<QStringList> info = sqlHelper->selectParameterIson(p);
    int count = info.size();
    QList<LabelButton *> btns = ui->gboxSelectParam->findChildren<LabelButton *>(QRegExp("^btnParam\\d+$"));
    for (int i = 0; i < btns.size(); ++i) {
        LabelButton *btnParam = btns.at(i);
        if (count > i) {
            btnParam->setName(info[i].at(ParameterIndex::PARAM_NAME));
            btnParam->setCode(info[i].at(ParameterIndex::PARAM_CODE));
            btnParam->setEnabled(true);
        } else {
            btnParam->setName(QString());
            btnParam->setCode(QString());
            btnParam->setEnabled(false);
        }
    }

    ui->btnStartExp->setEnabled(count != 0);
    ui->paramSelectedWidget->setCurrentIndex(count != 0 ? 0 : 1);
}

void Experiment::updateSampleDetail(Sample s)
{
    ui->lblSampleName1->setText(s.name);
    ui->lblSampleName2->setText(s.name);
}

void Experiment::updateMaterialDetail(Material m)
{
    ui->lblMaterialName1->setText(m.name);
    ui->lblMaterialSpec1->setText(m.type);
    ui->lblMaterialLot1->setText(m.batchNumber);
    ui->lblMaterialSerial1->setText(m.serialCode);
    ui->lblMaterialExpDate1->setText(m.expirationDate);
}

void Experiment::updateParameterDetail(Parameter p)
{
    ui->lblParamName->setText(p.name);
    ui->lblParamCode->setText(p.code);
    ui->lblParamName2->setText(p.name);
    ui->lblParamCode2->setText(p.code);
}

void Experiment::on_btnScanSample_clicked()
{
    if (SystemPreset::instance()->getBoolData(EnumPresetItem::OpenAutoFillBarCode))
    {
        int r = random(0, 0xffff);
        QString name = QString("Test-%1").arg(r, 4, 16, QLatin1Char('0'));
        ui->lineSampleName->setText(name);
    }
}

void Experiment::on_btnScanMaterial_clicked()
{
    if (SystemPreset::instance()->getBoolData(EnumPresetItem::OpenAutoFillBarCode))
    {
        QString batchNumber = QString("%1").arg(random(0, 999), 3, 10, QLatin1Char('0'));
        QString serialCode = QString("%1").arg(random(0, 9999), 4, 10, QLatin1Char('0'));
        QString pDate = QDate::currentDate().toString("yyyyMMdd");
        QString sn = "XPM123" + pDate + "36" + batchNumber + serialCode;
        ui->lineMaterialISN->setText(MaterialChecker::encodeMaterial(sn));
        fillMaterialInfo();
    }
}

void Experiment::on_btnClearSample_clicked()
{
    sample.name.clear();
    ui->lineSampleName->clear();
    ui->lineSampleName->setFocus();
}

void Experiment::on_btnClearMaterial_clicked()
{
    ui->lineMaterialISN->clear();
    ui->lineMaterialISN->setFocus();
    fillMaterialInfo();
}

void Experiment::insertExpInfoSlot(const ExperimentInfo &expInfo)
{
    sqlHelper->insertExperimentInfo(expInfo);
}

void Experiment::updateExpDuration()
{
    qint64 diff = expStartTime.secsTo(QDateTime::currentDateTime());
    ui->lblExpRunningTime->setText(QTime(0, 0).addSecs(diff).toString("hh:mm:ss"));

    const qint64 detal = 1; // 显示的最小时间 1s
    qint64 remainDiff = qMax((qint64)duration - diff, detal);
    QString remainTime = QTime(0, 0).addSecs(remainDiff).toString("hh:mm:ss");
    ui->lblExpDurationTime->setText(remainTime);

    int progressTotal = ui->mask->height();
    double progressCurrent = (duration > 0) ? (diff * progressTotal * 1.0) / duration : 0;
    progressCount = qMin(qRound(progressCurrent), progressTotal);

    if (liquidDetected && !continueExp && !autoStop)
    {// 液位传感器检测到液体，开始计时
        if (diff > maxRuntime)
        {// 超过最大运行时长，停止实验
            expDurationTimer->stop();
            expDurationPaintTimer->stop();
            autoStop = true;
            sendStopCmd();
            LOGFMTD("user: %s beyond max runtime, Auto end experiment", currentUsername.toStdString().data());
        }
    }
}

void Experiment::expPassSlot(const HostReply &data)
{
    if (manualStop)
    {// 主动停止实验，不提示
        return;
    }

    //if pass
    updateExperimentInfo(true);
    bdf->insertExpInfo(expInfo);
    buildExpEndChart();

    LOGFMTD("user: %s Experiment Finished", currentUsername.toStdString().c_str());

    double volume = expInfo.volume;
    double timediff = data.parameter.toInt() / 60.0;
    double flowRate = (timediff > 0) ? volume / timediff : 0;
    QString flowStr = QString("%4: %1ml, %5: %2min, %6: %3ml/min").arg(volume).arg(timediff).arg(flowRate).arg(tr("volume")).arg(tr("timediff")).arg(tr("flowRate"));
    UserLogger::log(flowStr);
    if (continueExp)
    {// 液位传感器超时，继续实验
        UserLogger::log(QString("%1, %2:[%3ml]%4").arg(data.message).arg(tr("By Volume")).arg(expInfo.volume).arg(tr("End Experiment")));
    }

    UserLogger::log(QString("%1: %2").arg(tr("Experiment Finished")).arg(sample.name));
    bdf->changeExperimentPageIndex(2);

    stop(true);
}

void Experiment::shkCheckResultSlot(const HostReply &result)
{
    if (result.parameter.toInt() == 0)
    {// 检查OK，可以开始电击
        sendAutoCmd();
        return;
    }

    qDebug() << "shkCheckResultSlot " << result.message << endl;
    expFailSlot(result);
}

void Experiment::setBtnParamCheckedSlot(bool checked)
{
    LabelButton * btnLabel = static_cast<LabelButton*>(sender());
    setBtnParamChecked(btnLabel, checked);
}

void Experiment::setBtnParamChecked(LabelButton *btnLabel, bool checked)
{
    if (checked)
    {
        btnLabel->getNameLabelInstance()->setStyleSheet(labelNameStyleChecked);
        btnLabel->getCodeLabelInstance()->setStyleSheet(labelCodeStyleChecked);
        parameter.name = btnLabel->getName();
    }
    else
    {
        btnLabel->getNameLabelInstance()->setStyleSheet(labelNameStyleUnchecked);
        btnLabel->getCodeLabelInstance()->setStyleSheet(labelCodeStyleUnchecked);
    }
}

void Experiment::retranslateUiSlot()
{
    ui->retranslateUi(this);
    frmInput2019::Instance()->retranslateUiSlot();
}

void Experiment::expFailSlot(const HostReply &data)
{
    if (manualStop)
    {// 主动停止实验
        stop(true);
        updateExperimentInfo(false);
        bdf->insertExpInfo(expInfo);
        ui->btnEndExp->setText(tr("Stopped"));
        ui->btnEndExp->setProperty("enabled", true);

        HostReply error = {};
        error.message = tr("Emergency Stop Success");
        UserLogger::log(QString("%1: %2").arg(error.message).arg(sample.name));

        MsgBox::information(this, tr("Tips"), tr("The shock stopped successfully"));
        bdf->changeExperimentPageIndex(0);
        ui->btnEndExp->setText(tr("Stop"));
        ui->btnEndExp->setEnabled(true);
        return;
    }

    if (autoStop)
    {// 超时自动停止
        HostReply result = {};
        result.parameter = maxRuntime;
        expPassSlot(result);
        return;
    }

    updateExperimentInfo(false);
    LOGFMTD("user: %s experiment fail", currentUsername.toStdString().c_str());
    ExpFail::DialogCode result = ExpFail().show(data);
    if (result == ExpFail::Accepted)
    {// 接受了故障，认为实验失败
        bdf->insertExpInfo(expInfo);
        bdf->changeExperimentPageIndex(0);
        UserLogger::log(QString("%1: %2").arg(tr("Experiment Failed")).arg(sample.name));
        stop(false);
    }
    else
    {// 认为实验正常，继续实验
        sendContinueCmd();
        LOGFMTD("user: %s user select 'continue experiment'", currentUsername.toStdString().c_str());
        UserLogger::log(QString("%1, %2:[%3ml]%4").arg(data.message).arg(tr("By Volume")).arg(expInfo.volume).arg(tr("Continue Experiment")));
    }
}

void Experiment::on_btnClearVolume_clicked()
{
    ui->lineVolume->clear();
    ui->lineVolume->setFocus();
}

void Experiment::on_btnPreviewReport_clicked()
{
    bdf->showReportPage(expInfo);
}

void Experiment::on_btnContinueExp_clicked()
{
    bdf->changeExperimentPageIndex(0);
}

void Experiment::initExperimentInfo()
{
    expStartTime = expEndTime = QDateTime::currentDateTime();
    QString strCurrentDt = expStartTime.toString(FMT_S);

    expInfo.username = currentUsername;
    expInfo.datetimeExpStart = strCurrentDt;
    expInfo.datetimeExpEnd = strCurrentDt;
    expInfo.parameterName = parameter.name;
    expInfo.parameterCode = parameter.code;
    expInfo.sampleName = sample.name;
    expInfo.materialISN = material.serialCode;
    expInfo.duration = "00:00:00";
    expInfo.result = "";
    expInfo.voltageRmsDatapath = ChartUtils::makeFilePath(expStartTime, sample.name);
    expInfo.specifications = material.type;
    expInfo.batchNumber = material.batchNumber;
    expInfo.volume = ui->lineVolume->value();
    expInfo.voltage = parameter.volt;
    expInfo.pulse = parameter.pulse;

    ui->lblExpRunningTime->setText(expInfo.duration);
    ui->lblExpStartTime->setText(expStartTime.toString(FMT_S));
    QString volumeText = ui->lineVolume->text() + "ml";
    ui->lblVolume->setText(volumeText);
    ui->lblVolume2->setText(volumeText);

    duration = 0;
    ui->lblExpDurationTime->clear();

    progressCount = 0;
    expDurationTimer->start();
    expDurationPaintTimer->start();

    QString info;
    info = QString(">%1: %2ml, %3: %4(%5)\n>%6: %7, %8: %9, %10: %11")
                .arg(tr("Volume")).arg(volumeText)
                .arg(tr("Parameter")).arg(parameter.name).arg(parameter.code)
                .arg(tr("Material Type")).arg(material.type)
                .arg(tr("batchNumber")).arg(material.batchNumber)
                .arg(tr("serialCode")).arg(material.serialCode);
    UserLogger::log(QString("%1: %2\n%3").arg(tr("Start Experiment")).arg(sample.name).arg(info));

    if (reuseSample)
    {// 样本条码重复使用，记录日志
        UserLogger::log(QString("%1: %2").arg(tr("Sample barcode use again")).arg(sample.name));
    }
}

void Experiment::updateExperimentInfo(bool success)
{
    expDurationTimer->stop();
    expDurationPaintTimer->stop();
    expEndTime = QDateTime::currentDateTime();

    expInfo.datetimeExpEnd = expEndTime.toString(FMT_S);
    expInfo.duration = QTime(0,0,0,0).addMSecs(expStartTime.msecsTo(expEndTime)).toString("hh:mm:ss");
    expInfo.result = success ? EXP_PASS : EXP_FAIL;

    ui->lblExpRunTotalTime->setText(expInfo.duration);
}

void Experiment::sendStartCmd()
{// 正常开始实验
    continueExp = false;

    ComCmd cmd;
    cmd.type = CMD_TYPE::HP2PAR;
    cmd.value1 = parameter.volt *gVoltRatio;
    cmd.value2 = parameter.pulse;
    cmd.value3 = parameter.interval;
    cmd.value4 = parameter.speed* 100 * gSpeedRatio;
    cmd.value5 = expInfo.volume * 100;
    bCtrl->sendPortCmd(cmd);
}

void Experiment::sendContinueCmd()
{// 液位传感器检测超时，继续实验
    continueExp = true;
    ComCmd cmd = {SHOCHK, 0, 0, 0, 0, 0};
    bCtrl->sendPortCmd(cmd);
}

void Experiment::sendStopCmd()
{// 紧急停止实验
    ComCmd cmd = {HP2STP, 0, 0, 0, 0, 0};
    bCtrl->sendPortCmd(cmd);
}

void Experiment::sendAutoCmd()
{// 开始电击，检查成功自动调用
    ComCmd cmd = {};
    cmd.type = continueExp ? HP3STR : HP2STR;
    bCtrl->sendPortCmd(cmd);
}

void Experiment::setMaterialUsed()
{
    QString qrcode = ui->lineMaterialISN->text().trimmed();
    if (!qrcode.isEmpty())
    {
        LOGFMTD("Set Material used: %s", qrcode.toStdString().c_str());
        sqlHelper->insertMaterial(qrcode);
    }
}

void Experiment::liquidDetectedSlot()
{// 液体流过液位传感器，设置耗材已使用
    if (!continueExp)
    {
        liquidDetected = true;
        setMaterialUsed();
    }
}

void Experiment::experimentStartSlot(const HostReply &result)
{
    // 更新倒计时总时长
    qint64 diff = expStartTime.secsTo(QDateTime::currentDateTime());
    duration = diff + result.parameter.toInt() + SystemPreset::instance()->getIntData(EnumPresetItem::FinishExpOffsetTimes);
    maxRuntime = duration + SystemPreset::instance()->getIntData(EnumPresetItem::StopExpDlayTimes);
    QString log = QString("User: %1, start experiment, predict run times: %2s").arg(currentUsername).arg(duration);

    if (continueExp)
    {// 液体检测超时，继续实验成功后，设置耗材已使用
        setMaterialUsed();

        // 重新开始倒计时
        progressCount = 0;
        expDurationTimer->start();
        expDurationPaintTimer->start();
    }
    else
    {// 液位传感器检测到液体启动实验，设置最长运行时间
        log += QString(", max run times: %1s").arg(maxRuntime);
    }

    LOGFMTD("%s", log.toStdString().data());
}
